package com.runa.persistence.service;

import com.alibaba.fastjson.JSON;
import com.runa.monitor.comm.dto.Meter;
import com.runa.monitor.comm.dto.Node;
import com.runa.monitor.comm.dto.newComm.AsynResult;
import com.runa.monitor.comm.entity.CommNetEquipment;
import com.runa.monitor.comm.entity.EntityErrorLog;
import com.runa.persistence.CollectionUtil;
import com.runa.persistence.DateUtil;
import com.runa.persistence.OutputUtil;
import com.runa.persistence.StringUtil;
import com.runa.persistence.check.DownTask;
import com.runa.persistence.check.DownTaskNetEquip;
import com.runa.persistence.map.FixedSizeMap;
import com.runa.persistence.repository.ErrorLogRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;


/**
 * 2024/7/2 13:55
 * zhangYB
 */
@Slf4j
@Service
@EnableScheduling
public class LogService {
    private static ErrorLogRepository errorLogRepository;

    private static Executor executor;

    private static Map<String, DownTask> downTaskMap = new ConcurrentHashMap<>();

    @Resource
    public void setErrorLogRepository(ErrorLogRepository errorLogRepository) {
        LogService.errorLogRepository = errorLogRepository;
    }

    @Resource(name = "asyncResponseScheduler")
    public void setExecutor(Executor executor) {
        LogService.executor = executor;
    }

    @Scheduled(cron = "0 0/1 * * * ?")
    public void calculate() {
        log.info("定时任务启动...");
        logSend();
        for (Map.Entry<String, DownTask> stringListEntry : downTaskMap.entrySet()) {
            String task = stringListEntry.getKey();
            DownTask downTask = stringListEntry.getValue();

            String node = "COMMNode:task:"+task;
            Map<String, EntityErrorLog> errorLogMap = errorLogRepository.findByNode(node).stream().collect(Collectors.toMap(EntityErrorLog::getIdentify, Function.identity()));

            String taskTime = downTask.getTaskTime();
            List<EntityErrorLog> errorLogs = new ArrayList<>();
            Map<String, DownTaskNetEquip> netEquMap = downTask.getNetEquMap();
            for (Map.Entry<String, DownTaskNetEquip> entry : netEquMap.entrySet()) {
                String netEquSerial = entry.getKey();
                DownTaskNetEquip downTaskNetEquip = entry.getValue();

                String identify = "网络设备:" + netEquSerial;
                EntityErrorLog errorLog = new EntityErrorLog();
                errorLog.setNode(node);
                errorLog.setIdentify(identify);
                errorLog.setEvent("TASK-"+downTaskNetEquip.check());
                errorLog.setTime(DateUtil.toStringGeneric(taskTime));
                errorLog.setContent(String.format("%s", downTaskNetEquip.toString()));
                errorLog.setRegs(downTaskNetEquip.check());
                errorLog.setOrderCode(downTaskNetEquip.getEquCount().toString()+":"+downTaskNetEquip.getQueuedCount());

                EntityErrorLog log = errorLogMap.get(identify);
                if (null != log) {
                    errorLog.setId(log.getId());
                }

                errorLogs.add(errorLog);
            }
            errorLogRepository.saveAll(errorLogs);

            String identify = "SUM:"+task;
            EntityErrorLog entityErrorLog = new EntityErrorLog();
            entityErrorLog.setNode(node);
            entityErrorLog.setIdentify(identify);
            entityErrorLog.setContent(downTask.toString());
            entityErrorLog.setRegs(downTask.getTaskTimeRecord());
            entityErrorLog.setEvent("TASK");
            entityErrorLog.setTime(DateUtil.toStringGeneric(taskTime));

            EntityErrorLog errorLog = errorLogMap.get(identify);
            if (null != errorLog) {
                entityErrorLog.setId(errorLog.getId());
            }
            errorLogRepository.save(entityErrorLog);

            if (downTask.finished()) {
                downTaskMap.remove(task);
            }

        }
    }

    public static void downTaskNetEquip(String task, List<CommNetEquipment> commNetEquipmentList) {
        DownTask downTask = downTaskMap.get(task);
        if (null == downTask) {
            return;
        }
        downTask.downTaskNetEquip(commNetEquipmentList);
    }

    public static void initTask(String task, List<String> ids, Integer priority) {
        if (null == task) {
            return;
        }
        downTaskMap.put(task, DownTask.initDownTask(task, priority, ids));
        saveReadTask(task, task, "读表", "COMMNode:task", "{} 读取: {} 条, 优先级: {}", DateUtil.milliTimeNow(), ids.size(), priority);
    }


    public static void pushQueue(String task, String netSerial, int size) {
        if (null == task) {
            return;
        }
        DownTask downTask = downTaskMap.get(task);
        if (null == downTask) {
            return;
        }
        downTask.queueCount(netSerial, size);
    }

    public static void pushQueue(String task, String netSerial) {
        pushQueue(task, netSerial, 1);
    }

    public static void result(AsynResult asynResult) {
        try {
            String task = asynResult.getTask();
            if (null == task) {
                return;
            }
            DownTask downTask = downTaskMap.get(task);
            if (null == downTask) {
                return;
            }
            downTask.result(asynResult);
        } catch (Exception e) {
            log.error("", e.getMessage());
        }
    }


    public static void upCount(String task, String netSerial, boolean result) {
        if (null == task) {
            return;
        }
        DownTask downTask = downTaskMap.get(task);
        if (null == downTask) {
            return;
        }
        if (result) {
            downTask.successUpCount(netSerial);
        } else {
            downTask.failedUpCount(netSerial);
        }
    }

    public static void pendingUpCount(String task, String netSerial) {
        if (null == task) {
            return;
        }
        DownTask downTask = downTaskMap.get(task);
        if (null == downTask) {
            return;
        }
        downTask.pendingUpCount(netSerial);
    }

    public static void downCount(String task, String netSerial, int size) {
        if (null == task) {
            return;
        }
        DownTask downTask = downTaskMap.get(task);
        if (null == downTask) {
            return;
        }
        downTask.downCount(netSerial, size);
    }

    public static void protocolNotMatch(String task, String netSerial, int size) {
        if (null == task) {
            return;
        }
        DownTask downTask = downTaskMap.get(task);
        if (null == downTask) {
            return;
        }
        downTask.protocolNotMatch(netSerial, size);
    }

    public static void saveReadTask(String task, String identify, String event, String format, Object... arguments) {
        saveTask(identify, event, "COMMNode:task:"+task+":READ", format, arguments);
    }

    public static void saveNetEquErrorTask(String task, String netSerial, String event, String format, Object... arguments) {
        DownTask downTask = downTaskMap.get(task);
        if (null != downTask) {
            DownTaskNetEquip downTaskNetEquip = downTask.getNetEquMap().get(netSerial);
            if (null != downTaskNetEquip) {
                format += ", 设备数量:" + downTaskNetEquip.getEquCount();
            }
        }
        saveTask(netSerial, event, "COMMNode:task:"+task+":ERROR", format, arguments);
    }

    public static void saveTask(String identify, String event, String node, String format, Object... arguments) {
        String content = String.format(format.replaceAll("\\{}", "%s"), arguments);
        saveErrLog(node, identify, event, dateTimeNow(), content, null, null);
    }

    public static void saveErrorLog(String identify, String event, String format, Object... arguments) {
        String content = String.format(format.replaceAll("\\{}", "%s"), arguments);
        saveErrLog("COMMNode", identify, event, dateTimeNow(), content, null, null);
    }

    public static void saveErrorLog(String identify, String event, byte[] data, int length) {
        saveErrorLog(identify, event, null, data, length);
    }

    private static void saveErrorLog(String identify, String event, String content, byte[] data, int length) {
        String regs = "";
        if (data != null) {
            regs = byteToHexString(data, 0, length);
        }
        saveErrLog("COMMNode", identify, event, dateTimeNow(), content, regs, null);
    }


    public static void saveErrLog(String node, String identify, String event, String time, String content, String regs, String orderCode) {
//        executor.execute(() -> {
//            EntityErrorLog entityErrorLog = new EntityErrorLog();
//            try {
//                entityErrorLog.setNode(node);
//                entityErrorLog.setEvent(event);
//                entityErrorLog.setIdentify(identify);
//                entityErrorLog.setTime(time);
//                entityErrorLog.setContent(content);
//                entityErrorLog.setRegs(regs);
//                entityErrorLog.setOrderCode(orderCode);
//                errorLogRepository.save(entityErrorLog);
//            } catch (Exception e) {
//                log.debug("saveErrLog: {}", e);
//                log.error("saveErrLog: {} {}", SerializeUtil.obj2json(entityErrorLog), e.getMessage());
//            }
//        });
    }

    public static final String dateTimeNow() {
        return parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date());
    }

    public static final String parseDateToStr(final String format, final Date date) {
        return new SimpleDateFormat(format).format(date);
    }

    public static String byteToHexString(byte[] b) {
        return byteToHexString(b, 0, b.length);
    }

    public static String byteToHexString(byte[] b, int offset, int length){
        StringBuilder sb = new StringBuilder();
        String tmp;
        int end = offset+length;
        if(offset>=0 && length>0 && end<=b.length) {
            for (int index=offset; index<end; index++) {
                tmp = Integer.toHexString(0xFF & b[index]);
                if (tmp.length() == 1) {
                    tmp = "0" + tmp;
                }
                sb.append(tmp);
            }
        }
        return sb.toString().toUpperCase();
    }

    private static Integer MAX_SIZE = 3;

    private static Map<String, Map<String, Map<String, List<Object>>>> taskMap = new FixedSizeMap<>(MAX_SIZE);

    public static void sendBefore(String taskId, String netSerial, Node node, Object data) {
    }


    private static Map<String, Map<String, List<Object>>> getNetSerialMap(String taskId) {
        Map<String, Map<String, List<Object>>> map = taskMap.get(taskId);
        if (null == map) {
            synchronized (taskMap) {
                if (null == map) {
                    map = new HashMap<>();
                    taskMap.put(taskId, map);
                }
            }
        }
        return map;
    }

    private static Map<String, List<Object>> getNodeMap(Map<String, Map<String, List<Object>>> netSerialMap, String netSerial) {
        Map<String, List<Object>> map = netSerialMap.get(netSerial);
        if (null == map) {
            map = new HashMap<>();
            netSerialMap.put(netSerial, map);
        }
        return map;
    }


    private static List<Object> getNode(Map<String, List<Object>> nodeMap, String nodeId) {
        List<Object> list = nodeMap.get(nodeId);
        if (null == list) {
            list = new ArrayList<>();
            nodeMap.put(nodeId, list);
        }
        return list;
    }


    private static Map<String, List<Object>> getNodeMap(String taskId, String netSerial) {
        Map<String, Map<String, List<Object>>> netSerialMap = getNetSerialMap(taskId);
        return getNodeMap(netSerialMap, netSerial);
    }

    private static List<Object> getNode(String taskId, String netSerial, String nodeId) {
        Map<String, List<Object>> nodeMap = getNodeMap(taskId, netSerial);
        return getNode(nodeMap, nodeId);
    }

    public static void sendAfterx(String taskId, String netSerial, Node node, Object data) {
        if (StringUtil.isBlank(taskId)) {
            return;
        }
        try {
            synchronized (taskMap) {
                List<Object> nodeList = getNode(taskId, netSerial, node.getId());
                Map<String, Object> m = new HashMap<>();
                m.put("date", DateUtil.milliTimeNow());
                m.put("node", node);
                if (data instanceof byte[]) {
                    byte[] bytes = (byte[])data;
                    m.put("message", byteToHexString(bytes));
                }
                nodeList.add(m);
            }
        } catch (Exception e) {
            log.error("", e);
        }
    }

    private static String[] taskIdArray = new String[10];

    private static Map<String, List<String>> map = new ConcurrentHashMap<>();

    private void logSend() {
        try {
            log.debug("开始输出...");
            List<String> outputList = new CopyOnWriteArrayList<>();
            for (String taskId : map.keySet()) {
                List<String> strings = map.get(taskId);
                if (CollectionUtil.isNotEmpty(strings)) {
                    outputList.addAll(strings);
                }
            }
            if (CollectionUtil.isNotEmpty(outputList)) {
                OutputUtil.output(outputList);
            }
            log.debug("结束输出...");
        } catch (Exception e) {
            log.error("{}", e.getMessage());
            log.debug("{}", e);
        }
    }

    private static synchronized boolean containTaskId(String taskId) {
        for (int i = 0; i < taskIdArray.length; i++) {
            if (taskIdArray[i] == null) {
                taskIdArray[i] = taskId;
                map.put(taskId, new CopyOnWriteArrayList<>());
                return true;
            } else if (taskIdArray[i].equals(taskId)) {
                return true;
            }
        }

        Integer min = null;
        String tmp = taskId;
        for (int i = 0; i < taskIdArray.length; i++) {
            if (Integer.valueOf(tmp) > Integer.valueOf(taskIdArray[i])) {
                tmp = taskIdArray[i];
                min = i;
            }
        }
        if (null != min) {
            map.remove(tmp);
            taskIdArray[min] = taskId;
            map.put(taskId, new CopyOnWriteArrayList<>());
            return true;
        }

        return false;
    }

    public static void sendAfter(String taskId, String netSerial, Node node, Object data) {
        try {
            sendAfter0(taskId, netSerial, node, data);
        } catch (Exception e) {
            log.error("{} {}", taskId, e.getMessage());
            log.debug("{}", e);
        }
    }

    public static void sendAfter0(String taskId, String netSerial, Node node, Object data) {
        if (!containTaskId(taskId)) {
            return;
        }
        if (StringUtil.isBlank(taskId)) {
            return;
        }
        Map<String, Object> m = new HashMap<>();
        m.put("date", DateUtil.milliTimeNow());
        m.put("node", node);
        if (data instanceof byte[]) {
            byte[] bytes = (byte[])data;
            m.put("message", byteToHexString(bytes));
        }
        List<String> list = map.get(taskId);
        if (null != list) {
            list.add(taskId + ": " + netSerial + ": " + node.getId() + ": date" + DateUtil.milliTimeNow() + "message: " + JSON.toJSONString(m));
        }
    }


    public static void main(String[] args) {
        Executor executor = asyncRequestScheduler();
        String s = "68 32 00 32 00 68 C9 01 10 11 27 00 02 7F 00 00 04 00 97 16 ";
        String taskId = "111";
        for (int i = 0; i < 1000; i++) {
            String netSerial = "netSerial" + (i+1);
            executor.execute(() -> {
                int j = 0;
                while (true) {
                    j++;
                    Node node = new Meter();
                    node.setId("id:"+ j);
                    node.setSerial("serial:"+ j);
                    sendAfter0(j+"", netSerial, node, hexStringToByte(s));
                    try {
                        TimeUnit.MILLISECONDS.sleep(10);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });

        }

        Timer timer = new Timer("下发输出");
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                log.info("开始输出...");
                List<String> outputList = new CopyOnWriteArrayList<>();
                for (String taskId : map.keySet()) {
                    List<String> strings = map.get(taskId);
                    if (CollectionUtil.isNotEmpty(strings)) {
                        outputList.addAll(strings);
                    }
                }
                if (CollectionUtil.isNotEmpty(outputList)) {
                    OutputUtil.output(outputList);
                }
                log.info("结束输出...");
            }
        }, 1000, 10 * 1000);






    }



    public static byte[] hexStringToBytes(String str) {
        if (str.contains(" ")) {
            str = str.replaceAll(" ", "");
        }
        int length = str.length();
        if (length % 2 == 1) {
            str = "0" + str;
        }
        return hexStringToByte(str);
    }

    public static byte[] hexStringToByte(String str) {
        if (str.contains(" ")) {
            str = str.replaceAll(" ", "");
        }
        byte[] bytes = new byte[str.length() / 2];
        for (int i = 0; i < str.length() / 2; i++) {
            String subStr = str.substring(i * 2, i * 2 + 2);
            bytes[i] = (byte) Integer.parseInt(subStr, 16);
        }
        return bytes;
    }

    public static Executor asyncRequestScheduler() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //配置核心线程数
        int prs = Runtime.getRuntime().availableProcessors();
        executor.setCorePoolSize(prs * 2);
        AtomicInteger i= new AtomicInteger();
        executor.setThreadFactory(r -> {
            Thread thread = new Thread(r);
            thread.setName("business-async-request-"+ i.getAndIncrement());
            return thread;
        });
        //配置最大线程数
        executor.setMaxPoolSize(executor.getCorePoolSize() * 2);
        //配置队列大小
        executor.setQueueCapacity(20480);
        //配置线程池中的线程的名称前缀
        executor.setThreadNamePrefix("business-async-request-");
        // rejection-policy：当pool已经达到max size的时候，如何处理新任务
        //        // CALLER_RUNS：不在新线程中执行任务，而是有调用者所在的线程来执行
        //Discard_Oldest: 丢弃旧的任务
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //执行初始化
        executor.initialize();
        return executor;
    }






}
